﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using Grundy.Common;
using Grundy.Entity;
using Grundy.Message;
using log4net;

namespace Grundy.Licensing.Server
{   
    public class LicenseSessionStore
    {
        private readonly LicensingStrategyFactory _licensingStrategyFactory;
        private readonly TimeSpan _cleanupOnceEvery;
        private ConcurrentBag<LicenseData> _licenses;
        private List<CustomerEntitlement> _otherCustomerEntitlements;
        private static ILog _logger = LogManager.GetLogger(typeof(LicenseSessionStore));

        public LicenseSessionStore(LicenseStore licenseStore, LicensingStrategyFactory licensingStrategyFactory, TimeSpan cleanupOnceEvery)
        {
            _licensingStrategyFactory = licensingStrategyFactory;
            _cleanupOnceEvery = cleanupOnceEvery;
            Initialize(licenseStore.OtherCustomerProductLicenseEntitlements, licenseStore.SelfCustomerProductLicenseEntitlements);
            InitializeMaintenanceTimer();
        }

        private void InitializeMaintenanceTimer()
        {
            new Timer(o => Cleanup(), null, 0, (int)_cleanupOnceEvery.TotalMilliseconds);
        }

        // Not thread safe
        private void Initialize(IEnumerable<CustomerLicenseEntitlement> otherCple, IEnumerable<CustomerLicenseEntitlement> selfCple)
        {
            _otherCustomerEntitlements = otherCple.Select( i => i.ToCustomerEntitlement()).Where(i => i != null).ToList();
            _licenses = new ConcurrentBag<LicenseData>();
            selfCple.ToList()
            .ForEach(i => _licenses.Add(new LicenseData(i, _licensingStrategyFactory)));
        }

        public virtual Guid Checkout(LicenseRequestInformation lri)
        {
            var matchingLicenses = FindMatchingLicenses(lri.FeatureIdentificationKey);
            if(matchingLicenses.Count == 0)
                throw new LicenseNotFoundException(lri.FeatureIdentificationKey);
            if(matchingLicenses.Count > 1)
                throw new AmbiguousLicenseException(matchingLicenses.Select(i => i.CustomerLicenseEntitlement).ToList());
            else
            {
                return matchingLicenses.First().Checkout(lri);
            }
        }

        public virtual bool Checkin(LicenseRequestInformation lri)
        {
            var license = _licenses.FirstOrDefault(i => i.HasClientSession(lri.ClientSessionId));
            if (license != null)
            {
                return license.Checkin(lri);
            }
            return false;
        }

        public virtual bool Reassert(LicenseRequestInformation lri)
        {
            var license = _licenses.FirstOrDefault(i => i.HasClientSession(lri.ClientSessionId));
            if (license != null)
            {
                return license.Reassert(lri.ClientSessionId, lri.EndPoint);
            }
            return false;
        }
        
        private List<LicenseData> FindMatchingLicenses(FeatureIdentificationKey fik)
        {
            var matcher = new FeatureIdentificationKeyMatcher(fik, new FeatureIdentificationKeyMatchScoreEvaluator());
            var matchingKeys = matcher.FindMatchingKeys(_licenses.Select(i => i.Key));
            var licenses = _licenses.Where(i => matchingKeys.Contains(i.Key)).ToList();
            return licenses;
        }

        public IEnumerable<LicenseData> Licenses
        {
            get { return _licenses; }
        }
        
        // Thread safe
        private void Cleanup()
        {
            try
            {
                _licenses.ForEachItemDo(i => i.CleanupClientSessions());
                _logger.Info("Finished cleaning up sessions.");
            }
            catch (Exception ex)
            {
                _logger.ErrorFormat("An error occurred while cleaning up sessions. Message: {0} \r\nDetails: {1}", ex.Message, ex);
            }
        }
    }
}